import tkinter
from tkinter import ttk


scale_factor = 1.5


def scale(**kwargs):
    for key in ['x', 'y', 'width', 'height']:
        if key in kwargs:
            kwargs[key] = int(kwargs[key] * scale_factor)
    return kwargs


def max_children_y_core(tkw, max_y=0, base_y=0):
    for child in tkw.children.values():
        y = child.winfo_y() + child.winfo_height() + base_y
        if y > max_y:
            max_y = y
        max_y = max_children_y_core(child, max_y, base_y + child.winfo_y())
    return max_y


class CommonAdapter:

    x = None
    y = None
    relx = None
    rely = None
    width = None
    height = None
    relwidth = None
    relheight = None
    anchor = None

    def __init__(self, master, tk_widget_type=ttk.Frame):
        self.TKW = tk_widget_type
        self.tkw = None
        self.master = master

    def master_tkw(self):
        if isinstance(self.master, CommonAdapter):
            return self.master.tkw
        else:
            return self.master

    def place(self):
        argv = {}
        for key in ['x', 'y', 'relx', 'rely', 'width', 'height', 'relwidth', 'relheight', 'anchor']:
            if self.__getattribute__(key) is not None:
                argv[key] = self.__getattribute__(key)
        self.tkw.place(**scale(**argv))

    def display(self):
        self.tkw = self.TKW(self.master_tkw())
        self.place()

    def destroy(self):
        self.tkw.destroy()

    def max_children_y(self):
        return max_children_y_core(self.tkw) / scale_factor


class ContainerAdapter(CommonAdapter):

    def content(self, master):
        pass

    def display(self):
        self.tkw = self.TKW(self.master_tkw())
        self.place()
        self.content(self.tkw)


class Page(ContainerAdapter):

    relwidth = 1
    relheight = 1

    def __init__(self, master):
        CommonAdapter.__init__(self, master, ttk.Frame)


class PageManager:

    def __init__(self):
        self.pages = []  # Left is heap top.

    def open_page(self, page):
        self.pages.insert(0, page)
        page.display()
        return page

    def close_page(self):
        if len(self.pages) > 0:
            page = self.pages.pop(0)
            page.destroy()
            return page


class PagePort(ContainerAdapter, PageManager):

    def __init__(self, master):
        ContainerAdapter.__init__(self, master, ttk.Frame)
        PageManager.__init__(self)


class Container(ContainerAdapter):

    def __init__(self, master):
        ContainerAdapter.__init__(self, master, ttk.Frame)


class YScrollableContainer(ContainerAdapter):

    def __init__(self, master):
        ContainerAdapter.__init__(self, master, ttk.Frame)
        self.inner = Container(self)
        self.inner.relx = 0
        self.inner.rely = 0
        self.inner.relwidth = 1
        self.inner.width = -10
        self.inner.content = lambda m: self.content(m)
        self.scrollbar = None

    def display(self):
        self.tkw = self.TKW(self.master)
        self.place()

        def scroll_handle(*args):
            if args[0] == 'moveto':
                rely = float(args[1])
                if 0 <= rely <= 1 - self.tkw.winfo_height() / self.inner.tkw.winfo_height():
                    self.inner.tkw.place(y=-float(args[1]) * self.inner.tkw.winfo_height())
                    self.tkw.update()
                    self.inner.tkw.update()
                    self.update_scrollbar()

        self.scrollbar = ttk.Scrollbar(self.tkw, command=scroll_handle)
        self.scrollbar.place(**scale(relx=1, x=-10, rely=0, width=10, relheight=1))
        self.inner.display()
        self.update()
        self.tkw.bind('<Configure>', lambda e: self.update_scrollbar())

    def update_scrollbar(self):
        h1 = self.tkw.winfo_height()
        y = self.inner.tkw.winfo_y()
        h2 = self.inner.tkw.winfo_height()
        if - y + h1 > h2 > h1:
            self.inner.tkw.place(y=self.tkw.winfo_height() - self.inner.tkw.winfo_height())
        elif h1 >= h2:
            self.inner.tkw.place(y=0)
        h1 = self.tkw.winfo_height()
        y = self.inner.tkw.winfo_y()
        h2 = self.inner.tkw.winfo_height()
        self.scrollbar.set(-y / h2, (-y + h1) / h2)
        self.scrollbar.update()

    def update(self):
        self.tkw.update()
        self.inner.tkw.update()
        self.inner.tkw.place(**scale(height=self.inner.max_children_y()))
        # print(self.inner.max_children_y())
        self.tkw.update()
        self.inner.tkw.update()
        self.update_scrollbar()

    def to_bottom(self):
        h1 = self.tkw.winfo_height()
        h2 = self.inner.tkw.winfo_height()
        if h1 < h2:
            self.inner.tkw.place(y=self.tkw.winfo_height() - self.inner.tkw.winfo_height())
            self.update()
